Data Story Group Project#

Jannes de Waard 14363003
Olek Lobman 14638177
Mickey Kotterer 13464167
Olav Mestrum 14504863

Introduction#

Dit project richt zich op het verkennen van de invloed van het huishoudelijk inkomen op inkomensniveaus en de invloed van de woonplaats op het inkomensniveau. Er worden twee verschillende perspectieven onderzocht en geanalyseerd om een breder inzicht te krijgen in dit complexe onderwerp. Het eerste perspectief stelt dat als iemand opgroeit in een gezin met een hoog huishoudelijk inkomen de kans groter is dat deze persoon later ook veel gaat verdienen. Hierbij wordt er uitgegaan van het idee dat het huishoudelijk inkomen wordt gebruikt om het collegegeld te betalen en dat daardoor iemand uit een arm gezin niet naar een dure universiteit zou kunnen. Dit perspectief benadrukt het belang van het milieu waarin je opgroeit als voornaamste factor voor het bepalen van je inkomen later. Het tweede perspectief stelt dat het huishoudelijk inkomen niet van invloed is maar de staat waarin je woont van belang is. Hier ligt de nadruk op de hoogte van het collegegeld dat betaald moet worden door studenten, dit kan namelijk verschillen voor studenten die in de staat geboren zijn en studenten die uit een andere staat komen. Door middel van het analyseren van twee verschillende datasets, zal er een inzicht worden verkregen in deze perspectieven en de argumenten die ze ondersteunen. Het doel van dit project is om een grondige evaluatie van beide perspectieven te bieden, om zo meer duidelijkheid te krijgen over de factoren die invloed hebben op het inkomensniveau wat behaald kan worden na het afronden van een studie.

Dataset and preprocessing
#

Dataset 1: US Household Income Statistics
#

Link: https://www.kaggle.com/datasets/goldenoakresearch/us-household-income-stats-geo-locations
Beschrijving:
Deze dataset geeft inzicht in de inkomens per huishouden in de Verenigde Staten. Naast inzicht in het gemiddelde en mediane inkomen, geeft deze dataset ook informatie over het aantal huishoudens en de bevolkingsdichtheid op verschillende geografische niveaus, zoals staten, provincies en plaatsen. Deze informatie is nuttig om een analyse te maken van de inkomensongelijkheid in verschillende staten in de Verenigde Staten.

Ten eerste is de dataset gevonden op de Kaggle website. Vervolgens is er geanalyseerd uit welke kolommen deze dataset bestaat. Hieruit volgde dat alleen de “State” en “mean income” kolom nuttig zijn voor dit onderzoek.

Dataset 2: College tuition, diversity, and pay
#

Link: https://www.kaggle.com/datasets/jessemostipak/college-tuition-diversity-and-pay
Beschrijving:
Deze dataset bevat informatie over het collegegeld en de kosten per college/universiteit in de Verenigde Staten voor het academisch jaar 2018-2019. Hieronder vallen het type instelling, de duur van de opleiding, de staat en of het in-state of out-of-state is. Met behulp van verschillende tabellen in de dataset kunnen verbanden en inzichten worden verkregen over de kosten, diversiteit, trends en potentieel salaris na het afronden van hogescholen en universiteiten in de Verenigde Staten.

Deze dataset bestaat uit vijf csv files namelijk: tuition_income, tuition_cost, salary_potential, diversity_school en historical_tuition. De laatste is voor onze datastory niet van belang, daarom hebben we de overige sets gemerged om de belangrijke data in één dataframe te hebben.

Het prepareren van de datasets#

import pandas as pd
import plotly.graph_objs as go
import plotly.express as px

Inkomens dataset#

income_df = pd.read_csv('kaggle_income.csv', encoding='ISO-8859-1')

In de cel hierboven hebben wij een encoding toegevoegd omdat we niet de goede encoding hadden als default. Wij zijn hier achter gekomen met behulp van chatGPT. Het probleem wat zich voordeet luidt als volgt:

    209     else:
    210         kwargs[new_arg_name] = new_arg_value
--> 211 return func(*args, **kwargs)

File c:\Users\Olav\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\util\_decorators.py:331, in deprecate_nonkeyword_arguments..decorate..wrapper(*args, **kwargs)
    325 if len(args) > num_allow_args:
    326     warnings.warn(
    327         msg.format(arguments=_format_argument_list(allow_args)),
    328         FutureWarning,
    329         stacklevel=find_stack_level(),
    330     )
--> 331 return func(*args, **kwargs)

File c:\Users\Olav\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\io\parsers\readers.py:950, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
    935 kwds_defaults = _refine_defaults_read(
    936     dialect,
    937     delimiter,
...
File c:\Users\Olav\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\_libs\parsers.pyx:852, in pandas._libs.parsers.TextReader._tokenize_rows()

File c:\Users\Olav\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\_libs\parsers.pyx:1965, in pandas._libs.parsers.raise_parser_error()

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 20530: invalid continuation byte

Hieruit bleek dat er een aantal de encoding die gebruikt werd bij het inlezen van kaggle_income.csv verkeerd was. Om erachter te komen welke encoding goed is hebben wij een formule gekregen die dat kan detecteren met behulp van chardet. hieruit kwam ISO-8859-1 uit als encoding. Omdat dit notebook gerund moet kunnen worden zonder problemen hebben wij dit opgeslagen en de formule weggehaald. De formule die we gebruikt hebben is:


with open('kaggle_income.csv', 'rb') as f:
    raw_bytes = f.read()
    detected_encoding = chardet.detect(raw_bytes)['encoding']

Schoolgeld dataset#

tuition_income_df = pd.read_csv('tuition_income.csv')
tuition_cost_df = pd.read_csv('tuition_cost.csv')
salary_potential_df = pd.read_csv('salary_potential.csv')
historical_tuition_df = pd.read_csv('historical_tuition.csv')
diversity_df = pd.read_csv('diversity_school.csv')

Deze dataset kwam als bundel met vijf csv bestanden. Hiervan konden wij er 4 gebruiken en mergen met elkaar. Dit werd een heel grote dataset en er is vervolgens een keuze gemaakt uit de kolommen die wij wilden meenemen in deze story.

tidf = tuition_income_df[['name', 'state', 'total_price', 'year', 'net_cost','income_lvl']]
tcdf = tuition_cost_df[['name', 'state', 'state_code', 'room_and_board', 'in_state_tuition', 'out_of_state_tuition']]
spdf = salary_potential_df[['name', 'state_name', 'early_career_pay', 'mid_career_pay']]
ddf = diversity_df[['name', 'total_enrollment', 'state', 'category',]]

tuition_df = pd.merge(tidf, tcdf, on='name')
tuition_df = pd.merge(tuition_df, spdf, on='name')
tuition_df = pd.merge(tuition_df, ddf, on='name')

Visualisatie 1 & 2#

In deze visualisatie wordt er gekeken naar de verdeling van het inkomen in de verenigde staten.

De taartdiagram toont de verdeling van huishoudens in verschillende inkomensgroepen in de Verenigde Staten. De inkomensgroepen zijn bepaald door het instellen van grenswaarden, zoals bijvoorbeeld \(35.000, \)50.000, enzovoort. Elke groep wordt weergegeven als een gekleurde sector in de taartdiagram.

Uit de gegevens blijkt dat meer dan 60% van de Amerikaanse huishoudens een jaarinkomen van niet meer dan $70.000 heeft. Deze bevindingen werpen een belangrijke vraag op over de toegankelijkheid van hoger onderwijs in relatie tot het huishoudelijk inkomen. Als de informatie uit deze taartgrafiek vervolgens wordt vergeleken met gegevens over de gemiddelde prijs voor collegegeld per staat, wordt duidelijk dat een aanzienlijk deel van de Amerikaanse huishoudens hoogstwaarschijnlijk niet eens de financiële mogelijkheid heeft om te studeren aan een universiteit.

Door de combinatie van deze twee grafieken kan er een complex beeld geschetst worden van de uitdagingen waarmee Amerikaanse huishoudens te maken hebben bij het nastreven van hoger onderwijs. Een groot deel van de huishoudens verdient al laag inkomen, en de hoge kosten voor collegegeld kunnen het voor hen nog moeilijker maken om toegang te krijgen tot universitaire opleidingen. Dit is een goed argument voor ons eerste perspectief.

bins = [0, 35000, 50000, 70000, 100000, 150000, 100000000]

income_df['house_income_qcut'] = pd.cut(income_df['Mean'], bins=bins, labels=['<35K', '35K-50K', "50K-70K", '70K-100K', '100K-150K', '>150K'])

category_counts = income_df['house_income_qcut'].value_counts()

datapie = go.Pie(
    labels=category_counts.index,
    values=category_counts.values,
    hole=0.85,
    marker = dict(colors=px.colors.qualitative.T10),
    textinfo = 'label+percent')

layout = go.Layout(
    title='Percentage huishoudens dat binnen inkomensgroep valt',
    height=600,)

fig = go.Figure(data=datapie, layout=layout)
fig.show()

figuur 1

bins = [0, 35000, 50000, 70000, 100000, 150000, 100000000]

income_df['house_income_qcut'] = pd.cut(income_df['Mean'], bins=bins, labels=['<35K', '35K-50K', "50K-70K", '70K-100K', '100K-150K', '>150K'])


data = go.Pie(
    labels = income_df['house_income_qcut'],
    values = (income_df['Mean']),
    hole = 0.85,
    marker = dict(colors=px.colors.qualitative.T10),
    textinfo = 'label+percent')

layout = go.Layout(
    title = 'Cumulatief van geld bij inkomensgroepen huishouden',
    height = 600)

fig = go.Figure(data=data, layout=layout)
fig.add_trace(datapie)
fig.show()

figuur 2

In de taartdiagram hierboven is te zien hoeveel de inkomsgroepen cumulatief aan inkomen krijgen van het totaal. De lage inkomensgroepen hebben veel minder procent van het geld in bezit dan zij bij een gelijke verdeling zouden hebben. Om dit te illustreren is er een DataFrame gemaakt die de proporties en verschillen laat zien.

cumul_money = pd.Series([4.03, 13.7, 27.7, 29.3, 21, 4.33])
cumul_people = pd.Series([9.69, 21.3, 31.5, 23.8, 11.9, 1.75])
index = ['<35K', '35K-50K', "50K-70K", '70K-100K', '100K-150K', '>150K']

pie_df = pd.DataFrame({'income_scale' : index, 'percentage_of_money' : cumul_money, 'percentage_of_people' : cumul_people})
pie_df['difference'] = (pie_df['percentage_of_money'] - pie_df['percentage_of_people'])
pie_df['proportion'] = round((pie_df['percentage_of_money'] / pie_df['percentage_of_people']), 2)
display(pie_df)
income_scale percentage_of_money percentage_of_people difference proportion
0 <35K 4.03 9.69 -5.66 0.42
1 35K-50K 13.70 21.30 -7.60 0.64
2 50K-70K 27.70 31.50 -3.80 0.88
3 70K-100K 29.30 23.80 5.50 1.23
4 100K-150K 21.00 11.90 9.10 1.76
5 >150K 4.33 1.75 2.58 2.47

Uit deze tabel is op te maken dat de lagere inkomensgroepen een kleinere proportie hebben van de totale inkomens. Hieruit is op te maken dat lagere inkomensgroepen weinig geld te besteden hebben op bijvoorbeeld onderwijs. Doordat de prijs van onderwijs kan verschillen zijn zij beperkt in het kiezen van een universiteit

** Tabel visualiseren

Visualisatie 3:#

Deze staafgrafiek toont het gemiddelde huishoudelijke inkomen per staat. De gegevens zijn afkomstig van het dataframe ‘income_df’ en bevatten de kolommen ‘State_Name’ (naam van de staat) en ‘Mean’ (gemiddeld inkomen).

De staafdiagram is opgebouwd uit verticale balken, waarbij elke staaf overeenkomt met een staat. De horizontale as representeert de namen van de staten, en de verticale as geeft het gemiddelde huishoudelijke inkomen weer. Door de hoogtes van de staven te vergelijken, krijgen we een beeld van de relatieve inkomensniveaus tussen de verschillende staten.

In de grafiek valt te zien dat er tussen staten grote verschillen in gemiddeld huishoudelijk inkomen zijn. Dit suggereert dat de staat waarin iemand geboren wordt in een directe relatie staat tot het inkomensniveau, wat ons tweede perspectief ondersteunt. In de staten waarin het gemiddelde huishoudelijke inkomen hoger is, kunnen families namelijk eerder de kosten voor onderwijs betalen, en beter onderwijs resulteert in een hoger gemiddeld inkomen. Toch hoeft dit hoge huishoudelijke inkomen niet de reden te zijn dat iemand veel verdiend, dit zal in de volgende visualisatie aangetoond worden.

tril_df = income_df[['State_Name', 'Mean']]
mid_df = tril_df.groupby('State_Name')[['Mean']].mean().reset_index()
mid_df = mid_df.sort_values('Mean', ascending=False)

mean_income = go.Bar(
    x = mid_df['State_Name'],
    y = mid_df['Mean'],
    name = 'Average househould income'
)


fig2 = go.Figure(data=mean_income)
fig2.show()

figuur 3

Visualisatie 4#

Verschil in collegegeld universiteiten#

Deze visualisatie is een scatterplot van de tweede dataset. In dit scatterplot staat elk puntje voor een universiteit in een bepaalde staat in de Verenigde Staten. Op de x-as is te lezen hoeveel collegegeld studenten aan deze universiteit betalen als ze zelf uit de staat komen waar de universiteit is gevestigd. Op de y-as is te lezen hoeveel collegegeld studenten betalen als ze studeren aan een universiteit die zich niet bevindt in de staat waarin ze wonen. Deze dataset bevat uitsluitend informatie over universiteiten waarbij studenten van buiten de staat meer moeten betalen dan studenten van binnen de staat. Dit is bij 224 van de 629 universiteiten het geval. Omgerekend is dat 35.6% van de universiteiten.

same_filt = (tuition_df['in_state_tuition'] != tuition_df['out_of_state_tuition'])

different_tuition_df = tuition_df[same_filt]

fig = px.scatter(different_tuition_df, x=different_tuition_df['in_state_tuition'], y=different_tuition_df['out_of_state_tuition'],
	            color=different_tuition_df['state_name'],
                hover_name=different_tuition_df['name'], log_x=True, size_max=60
                )

fig.update_layout(
    xaxis=dict(title='Tuition for in state students'),
    yaxis=dict(title='Tuition out of state students'),
    title='Scatter Plot of Universities only with different tuition fees for out of state studenten'
)

fig.show()

figuur 4

Visualisatie 5#

Doordat 35.6% van de universiteiten verschillende tuition fees hebben, zijn zij bij elkaar verantwoordelijk voor de verschuiving in het onderstaande scatterplot. Deze laat een gestippelde lijn zien bij een gelijke verdeling van tuition fee’s. We hebben de gemiddelden van tuition fees for ‘in state’ en ‘out of state’ studenten gepakt per staat. Daaruit is de onderstaande plot ontstaan

lil_df = tuition_df[['state_y', 'in_state_tuition', 'out_of_state_tuition']]
mean_df = lil_df.groupby('state_y')[['in_state_tuition', 'out_of_state_tuition']].mean().reset_index()

fig2 = px.scatter(mean_df, x=mean_df['in_state_tuition'], y=mean_df['out_of_state_tuition'],
	            color=mean_df['state_y'],
                hover_name=mean_df['state_y'],
                )
fig2.add_trace(go.Scatter(x=[5000, 55000],
                        y=[5000, 55000],
                        mode='lines',
                        name='Equal tuition fees',
                        line=dict(color='black', dash='dash')))
fig2.update_layout(xaxis_title='In state tuition fee', yaxis_title='Out of state tuition fee')

figuur 5

Visualisatie 6#

In de onderstaande interactieve landkaart is een combinatie van bovenstaande grafieken en het gemiddeld huishoudelijk inkomen te vinden over de verschillende staten van de Verenigde Staten. De kaart geeft naast het gemiddeld huishoudelijk inkomen per staat ook de gemiddelde in- en out- state tuition en het verschil uitgedrukt in percentage.

Wat opvalt is dat staten waar zowel in- als out of state studenten veel tuition fee betalen vaak ook een hoog gemiddeld huishoudelijk inkomen hebben. Toch zijn er staten, bijvoorbeeld Indiana, waar het tuition fee voor in state studenten hoog is zonder een hoog gemiddeld inkomen. Dit betekent dat het lastig kan zijn om de kosten voor onderwijs te betalen en veel te verdienen als je bent geboren in een staat waar het gemiddeld inkomen laag is en de tuition fee hoog. Ons tweede perspectief wordt hierdoor versterkt.

lil_df = tuition_df[['state_y', 'in_state_tuition', 'out_of_state_tuition']]
mean_df = lil_df.groupby('state_y')[['in_state_tuition', 'out_of_state_tuition']].mean().reset_index()
mean_df = mean_df.rename(columns={'state_y' : 'State_Name'})

tril_df = income_df[['State_Name', 'Mean']]
mid_df = tril_df.groupby('State_Name')[['Mean']].mean().reset_index()
filt = (mid_df['State_Name'] != 'District of Columbia') & (mid_df['State_Name'] != 'Puerto Rico')
mid_df = mid_df[filt]

merged_df = pd.merge(mean_df, mid_df, on='State_Name')

merged_df['difference_percentage'] = (abs(merged_df['out_of_state_tuition'] - merged_df['in_state_tuition']) / merged_df['in_state_tuition']) * 100

code = {'Alabama': 'AL','Alaska': 'AK','Arizona': 'AZ',
        'Arkansas': 'AR','California': 'CA','Colorado': 'CO','Connecticut': 'CT','Delaware': 'DE',
        'District of Columbia': 'DC','Florida': 'FL','Georgia': 'GA','Hawaii': 'HI','Idaho': 'ID',
        'Illinois': 'IL','Indiana': 'IN','Iowa': 'IA','Kansas': 'KS','Kentucky': 'KY','Louisiana': 'LA',
        'Maine': 'ME','Maryland': 'MD','Massachusetts': 'MA','Michigan': 'MI','Minnesota': 'MN',
        'Mississippi': 'MS','Missouri': 'MO','Montana': 'MT','Nebraska': 'NE','Nevada': 'NV',
        'New Hampshire': 'NH','New Jersey': 'NJ','New Mexico': 'NM','New York': 'NY',
        'North Carolina': 'NC','North Dakota': 'ND','Ohio': 'OH','Oklahoma': 'OK',
        'Oregon': 'OR','Pennsylvania': 'PA','Rhode Island': 'RI','South Carolina': 'SC',
        'South Dakota': 'SD','Tennessee': 'TN','Texas': 'TX','Utah': 'UT','Vermont': 'VT','Virginia': 'VA',
        'Washington': 'WA','West Virginia': 'WV','Wisconsin': 'WI', 'Wyoming': 'WY'}

merged_df['State_Code'] = merged_df['State_Name'].map(code)

fig = px.choropleth(
    merged_df,
    locations='State_Code',
    color='Mean',
    color_continuous_scale=[[0, 'white'], [1, 'darkgreen']],  
    hover_name='State_Name',
    locationmode='USA-states',
    scope='usa',
    labels={'Mean': 'Mean Household Income'}
)

fig.update_traces(
    hovertemplate='<b>%{hovertext}</b><br>' +
                  'Mean: $%{z:.2f}<br>' +
                  'In-State Tuition: $%{customdata[0]:,.2f}<br>' +
                  'Out-of-State Tuition: $%{customdata[1]:,.2f}<br>' + 
                  'Difference percentage : %{customdata[2]:,.2f}%',
    customdata=merged_df[['in_state_tuition', 'out_of_state_tuition', 'difference_percentage']].values.tolist()
)

fig.update_layout(
    title={
        'text': 'Mean Household Income per State',
        'xanchor': 'center',
        'yanchor': 'top',
        'x': 0.5
    },
    coloraxis_colorbar=dict(title='Mean Household Income'),
)

fig.show()

figuur 6

Visualisatie 7#

In de onderstaande scatterdiagram van de tweede dataset valt de correlatie te zien tussen Career pay en Tuition fee voor in-state tuition en out-of-state tuition. Met Career pay wordt het gemiddelde verwachte salaris bedoeld voor een student van een bepaalde universiteit. Hierbij wordt er dus geen onderscheid gemaakt tussen verschillende studies binnen een universiteit.

In de grafiek is af te lezen dat studenten die meer dan $50 000 tuition betalen, gemiddeld meer verdienen dan studenten die minder tuition betalen. Hieruit kan dus geconcludeerd worden dat het betalen van hoge kosten voor tuition een indicator is voor een hoger inkomen, dit ondersteunt ons eerste perspectief. Echter betekent dit niet dat het betalen van weinig tuition hoe dan ook resulteert in een laag inkomen. Dit is ook af te lezen in de grafiek, er zijn namelijk meerdere gevallen van lage kosten voor tuition maar hoge inkomens. Verder zou de onderstaande scatterdiagram ook gebruikt kunnen worden voor het analyseren van de ongelijkheid die ontstaat door het grote verschil tussen in en out-of state tuition.

Zo kan er bijvoorbeeld iemand met een hoog huishoudelijk inkomen, die veel collegegeld betaalt, naar dezelfde universiteit gaan als iemand met een laag huishoudelijk inkomen die weinig collegegeld betaalt. Doordat degene met een hoog huishoudelijk inkomen studeert aan een universiteit die zich buiten de staat waarin deze persoon woont bevindt.

sub_df = tuition_df[['name','in_state_tuition', 'out_of_state_tuition', 'mid_career_pay']]

fig = px.scatter(
    sub_df,
    x = sub_df['mid_career_pay'],
    y = [sub_df['in_state_tuition'], sub_df['out_of_state_tuition']]
    #labels={'mid_career_pay' : 'Estimated career pay', 'in_state_tuition' : 'In state tuition', 'out_of_state_tuition' : 'Out of state tuition'}
)

fig.update_traces(
    hovertemplate='<b>%{customdata[0]}</b><br>' +
                  'In State tuition Fee: $%{customdata[3]}<br>' +
                  'Out-of-State Tuition Fee: $%{customdata[2]}<br>' +
                  'Estimated career pay: $%{customdata[1]}',
    customdata=sub_df[['name','mid_career_pay', 'out_of_state_tuition', 'in_state_tuition']].values.tolist()
)
fig.show()

Reflection#

Over het algemeen werden de visualisaties goed ontvangen, maar er waren enkele punten die we moesten aanpassen. Ten eerste moesten we de volgorde van visualisatie 3 verbeteren en van visualisatie 6 een interactieve visualisatie maken. Bovendien werd opgemerkt dat onze beschrijvingen en argumenten nog niet volledig op orde waren. Er waren te weinig argumenten om onze perspectieven te ondersteunen. Na het ontvangen van deze feedback hebben we besloten om ons onderwerp te veranderen en andere perspectieven te kiezen. De reden hiervoor was dat ons eerdere perspectieven en onderwerp niet aansloten op onze datasets. Na het onderwerp en de perspectieven te hebben aangepast, is het toch gelukt om de juiste argumenten te bedenken bij de visualisaties.

Work Distribution#

Taak

Uitvoerder

Juiste datasets vinden

Micky, Olav, Jannes

Perspectieven bedenken

Jannes

Introductie

Jannes & Olek

Visualisatie 1

Olav

Visualisatie 2

Olav

Visualisatie 3

Olek

Visualisatie 4

Olav

Visualisatie 5

Olav

Visualisatie 6

Olav

Visualisatie 7

Olek

Beschrijving 1&2

Olek

Beschrijving 3

Micky

Beschrijving 4,5&6

Micky

Beschrijving 7

Jannes

Jupyter notebook samenvoegen

Olav

Jupyterbook Pushen op github

Olav

Reflectie

Jannes

figuur 7